package com.tbit.uqbike.service.business.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.tbit.common.entity.UserOpLog;
import com.tbit.common.utils.Utf8Mb4Checker;
import com.tbit.uqbike.constant.*;
import com.tbit.uqbike.constant.enums.AccountDayStatEnum;
import com.tbit.uqbike.enums.SystemCodeEnum;
import com.tbit.uqbike.manager.exception.BaseException;
import com.tbit.uqbike.object.business.UserInfo;
import com.tbit.uqbike.object.pojo.*;
import com.tbit.uqbike.object.pojo.dto.BatchRegisterAuthDTO;
import com.tbit.uqbike.object.pojo.dto.UserDTO;
import com.tbit.uqbike.object.pojo.vo.UserVO;
import com.tbit.uqbike.object.value.LoginInfo;
import com.tbit.uqbike.object.view.NameObject;
import com.tbit.uqbike.service.base.RedisService;
import com.tbit.uqbike.service.business.*;
import com.tbit.uqbike.webmanager.business.user_op_log.service.UserOpLogService;
import com.tbit.uqbike.webmanager.controller.BaseController;
import com.tbit.uqbike.webmanager.dao.capital.*;
import com.tbit.uqbike.webmanager.dao.core.*;
import com.tbit.uqbike.webmanager.dao.log.UserPhoneLogDao;
import com.tbit.uqbike.webmanager.dao.stat.AccountDayStatDao;
import com.tbit.uqbike.webmanager.util.*;
import com.tbit.uqbike.webmanager.util.excel.ExcelUtil;
import com.tbit.uqbike.webmanager.util.file.FileUtils;
import com.tbit.uqbike.webmanager.util.redis.RedisUtil;
import com.tbit.utils.DateTimeUtil;
import com.tbit.utils.NullUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author chenmengran
 * @Description: 用户信息
 * @date 2019\12\16 001615:53
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private AccountService accountService;
    @Autowired
    private UserAccountLogService userAccountLogService;
    @Autowired
    private UserPhoneLogDao userPhoneLogDao;
    @Autowired
    private RideLogDao rideLogDao;
    @Autowired
    private RideLogDetailsDao rideLogDetailsDao;
    @Autowired
    private UserAccountMoneyDao userAccountMoneyDao;
    @Autowired
    private MoneyOpeLogDao moneyOpeLogDao;
    @Autowired
    private UserToAgentDao userToAgentDao;
    @Autowired
    private UserCapitalChangeDao userCapitalChangeDao;
    @Autowired
    private BalanceChangeDao balanceChangeDao;
    @Autowired
    private ArrearsLogDao arrearsLogDao;
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private UserDelayPayDao userDelayPayDao;
    @Autowired
    private AccountDayStatDao accountDayStatDao;
    @Autowired
    private RideUseLogDao rideUseLogDao;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private StudentAuthApplyDao studentAuthApplyDao;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserOpLogService userOpLogService;
    @Autowired
    private UserAuthLogService userAuthLogService;
    @Autowired
    private RideCardService rideCardService;
    @Autowired
    private VipCardService vipCardService;
    @Autowired
    private MemberFeeService memberFeeService;
    @Autowired
    private AgentDepositVipService agentDepositVipService;
    @Autowired
    private CouponService couponService;
    @Autowired
    private OrderFeeAppealService orderFeeAppealService;
    @Autowired
    private SuggestionService suggestionService;
    @Autowired
    private InvoiceRecordService invoiceRecordService;
    @Autowired
    private DepositApplyService depositApplyService;
    @Autowired
    private ParkPointApplyService parkPointApplyService;


    @Override
    public Integer queryCount(LoginInfo loginInfo, String phone, String name, String idNo, Integer nameAuth, Integer depositState, Long startTime, Long endTime) {
        //查询参数
        Map<String, Object> params = new HashMap<String, Object>();
        if (loginInfo.type.equals(UserConstant.USER_ADMIN)
                || loginInfo.type.equals(UserConstant.USER_BRAND)) {
            if (loginInfo.type.equals(UserConstant.USER_BRAND)) {
                params.put("accountId", loginInfo.id);
            }
        } else if (loginInfo.type.equals(UserConstant.USER_FRANCHISEE)
                || loginInfo.type.equals(UserConstant.USER_AGENT)) {
            List<Integer> accountIds = new ArrayList<>();
            if (loginInfo.type.equals(UserConstant.USER_FRANCHISEE)) {
                accountIds = accountService.getByParentId(loginInfo.id);
            } else {
                accountIds.add(loginInfo.id);
            }
            /*List<Integer> userIds = userToAgentService.getByAccountIds(accountIds);
            params.put("userIds", userIds);
            if (userIds==null || userIds.size()<1){
                return 0;
            }*/
            params.put("accountIds", accountIds);
        } else {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        params.put("name", name);
        params.put("idNo", idNo);
        params.put("phone", phone);
        params.put("nameAuth", nameAuth);
        params.put("depositState", depositState);
        if (startTime != null) {
            params.put("startTime", new Date(startTime));
        }
        if (endTime != null) {
            params.put("endTime", new Date(endTime));
        }
        return userDao.queryCount(params);
    }

    @Override
    public List<User> queryPage(LoginInfo loginInfo, String phone, String name, String idNo, Integer rowCount, Integer pageNO, Integer nameAuth, Integer depositState, Long startTime, Long endTime) {
        //查询参数
        Map<String, Object> params = new HashMap<>();
        if (loginInfo.type.equals(UserConstant.USER_ADMIN)
                || loginInfo.type.equals(UserConstant.USER_BRAND)) {
            if (loginInfo.type.equals(UserConstant.USER_BRAND)) {
                params.put("accountId", loginInfo.id);
            }
        } else if (loginInfo.type.equals(UserConstant.USER_FRANCHISEE)
                || loginInfo.type.equals(UserConstant.USER_AGENT)) {
            List<Integer> accountIds = new ArrayList<>();
            if (loginInfo.type.equals(UserConstant.USER_FRANCHISEE)) {
                accountIds = accountService.getByParentId(loginInfo.id);
            } else {
                accountIds.add(loginInfo.id);
            }
            params.put("accountIds", accountIds);
        } else {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        params.put("name", name);
        params.put("idNo", idNo);
        params.put("phone", phone);
        params.put("start", (pageNO - 1) * rowCount);
        params.put("rowCount", rowCount);
        params.put("nameAuth", nameAuth);
        params.put("depositState", depositState);
        if (startTime != null) {
            params.put("startTime", new Date(startTime));
        }
        if (endTime != null) {
            params.put("endTime", new Date(endTime));
        }
        List<User> users = userDao.queryPage(params);
        if (CollectionUtils.isEmpty(users) || !loginInfo.type.equals(UserConstant.USER_ADMIN)) {
            return users;
        }
        Map<String, Object> queryMap = new HashMap<>(4);
        queryMap.put("accountIds", users.stream().map(User::getAccountId).collect(Collectors.toSet()));
        Map<Integer, String> brandNameMap = accountDao.getByParams(queryMap).stream().collect(Collectors.toMap(Account::getAccountId, Account::getName));
        users.forEach(object -> {
            if (NullUtils.isEmpty(object.getRideMoney())) {
                object.setRideMoney(0);
            }
            object.setBrandName(brandNameMap.getOrDefault(object.getAccountId(), "-"));
        });
        return users;
    }

    @Override
    public List<User> getByAccountIdAndPhone(Integer accountId, String phone) {
        Map<String, Object> params = new HashMap<>(4);
        params.put("accountId", accountId);
        params.put("phone", phone);
        return userDao.getByAccountIdAndPhone(params);
    }

    @Override
    public List<User> getByAccountIdAndIdNo(Integer accountId, String idNo) {
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("idNo", idNo);
        return userDao.getByAccountIdAndIdNo(params);
    }

    @Override
    public User getByAccountPhone(Integer accountId, String phone) {
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("phone", phone);
        return userDao.getByAccountAndPhone(params);
    }

    @Override
    public User getByUserId(Integer userId) {
        return userDao.getByUserId(userId);
    }

    @Override
    public List<User> getByUserIdBatch(List<Integer> userIds) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("userIds", StringUtil.getListString(userIds));

        return userDao.getByUserIdBatch(params);
    }

    @Override
    public void updateMoney(Integer userId, Integer money, Integer rideMoney) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("userId", userId);
        params.put("money", money);
        params.put("rideMoney", rideMoney);
        params.put("updateTime", new Date());
        userDao.updateMoney(params);
    }

    @Override
    public void frozenDeposit(Integer userId, Integer depositState) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("userId", userId);
        params.put("depositState", depositState);
        params.put("updateTime", DateTimeUtil.getNowTime());

        userDao.frozenDeposit(params);
    }

    @Override
    public void updateDeposit(Integer userId, Integer deposit, Integer depositMoney) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("userId", userId);
        params.put("deposit", deposit);
        params.put("depositMoney", depositMoney);
        params.put("updateTime", DateTimeUtil.getNowTime());

        userDao.updateDeposit(params);
    }

    @Override
    public void updateStudentAuth(Integer userId, Integer studentAuth, String studentIdNo) {
        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("studentAuth", studentAuth);
        params.put("studentIdNo", studentIdNo);
        userDao.updateStudentAuth(params);
    }

    @Override
    public void updateNameAuth(Integer userId, String name, String idNO, LoginInfo login) {
        User user = userDao.getByUserId(userId);
        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("name", name);
        params.put("idNO", idNO);
        params.put("updateTime", DateTimeUtil.getNowTime());
        String remark = user.getRemark() + "(人工认证)";
        params.put("remark", remark);
        userDao.updateNameAuth(params);

        // 插入用户操作日志
        userOpLogService.insertSingle(UserOpLog.builder()
                .accountId(user.getAccountId())
                .opUser(login.getName())
                .opUserPhone(login.getPhone())
                .targetUser(user.getName())
                .targetUserPhone(user.getPhone())
                .operation("用户账号管理")
                .opType("人工认证")
                .opPlatform(login.getLoginUserPlatform())
                .opTime(LocalDateTime.now())
                .result(true)
                .build());
    }

    @Override
    public Long getUserFee(String phone, LoginInfo loginInfo) {
        User user = getByAccountPhone(loginInfo.id, phone);
        if (user == null) {
            throw new BaseException(SystemCodeEnum.NOT_EXIST);
        }
        String json = "";
        try {
            List<NameValuePair> params = new ArrayList<>();
            String path = "/properties/baseParams.properties";
            String url = null;
            url = ConfigUtil.getInstance(path).getStringValue("userfee.url");
            params.add(new BasicNameValuePair("userId", JsonUtils.obj2Json(user.getUserId())));
            json = HttpClientUtils.httpPost(url, null, params);
        } catch (IOException e) {
            LogUtil.warn("查询用户费用失败：" + e.toString());
            throw new BaseException(SystemCodeEnum.CALL_OVER_TIMES);
        }
        return 1L;
    }

    @Override
    public List<User> getByAccountAndIdNO(Integer accountId, String idNO) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("accountId", accountId);
        params.put("idNO", idNO);
        return userDao.getByAccountAndIdNO(params);
    }

    @Override
    public void recharge(Integer userId, Integer money, String remark, LoginInfo loginInfo) {
        User user = getByUserId(userId);
        if (user == null) {
            throw new BaseException("user_not_exist");
        }
        Integer oldUserMoney = user.getMoney();
        Integer newUserMoney = oldUserMoney + money;
        if (newUserMoney < 0) {
            throw new BaseException("用户余额不足");
        }
        //修改余额前判断备注否包含特殊字符
        if (Utf8Mb4Checker.containsUtf8mb4Chars(remark)) {
            throw new BaseException("备注含非法字符，请调整后再次发起");
        }
        Date operaTime = new Date();
        boolean payRideMoney = false;
        int oldRideMoney = user.getRideMoney();
        if (oldRideMoney != 0 && newUserMoney >= user.getRideMoney()) {
            //用户有欠款，说明有订单未支付，并且修改后金额足以支付这笔订单
            payRideMoney = true;
            newUserMoney = newUserMoney - user.getRideMoney();
            user.setRideMoney(0);
        }
        userDao.updateUserMoney(userId, newUserMoney.longValue(), (long) user.getRideMoney(), operaTime);
        moneyOpeLogDao.insert(new MoneyOpeLog(user.getAccountId(), 1, money > 0 ? 1 : 0, Math.abs(money), operaTime, userId, user.getPhone(), loginInfo.getAccountUserId(), loginInfo.getPhone(), remark));
        userAccountLogService.insert(new UserAccountLog(userId, user.getAccountId(), 0, money < 0 ? 0 : 1, Math.abs(money), operaTime, remark));
        //获取用户最新绑定的区域
        UserToAgent userToAgent = userToAgentDao.getByUserId(userId);
        //如果没有绑定，则为未知区域
        Integer accountId = userToAgent == null ? -1 : userToAgent.getAccountId();
        //原区域id
        Integer originalAccountId = -1;
        if (userToAgent != null && userToAgent.getFirstId() != null){
            originalAccountId = userToAgent.getFirstId();
        }
        if (money > 0) {//充值
            UserAccountMoney userAccountMoney = userAccountMoneyDao.selectOne(new QueryWrapper<UserAccountMoney>().eq("userId", userId).eq("accountId", accountId).eq("moneyType", 1));
            Long startMoney = userAccountMoney == null ? 0 : userAccountMoney.getMoney();
            if (userAccountMoney == null) {
                //新增区域余额
                userAccountMoney = new UserAccountMoney(userId, user.getPhone(), accountId, user.getAccountId(), money.longValue(), operaTime.getTime(), 1, remark);
                userAccountMoneyDao.insert(userAccountMoney);
            } else {
                //更新区域余额
                userAccountMoney.setMoney(userAccountMoney.getMoney() + money.longValue()).setRemark(remark).setUpdateTime(operaTime.getTime());
                userAccountMoneyDao.update(userAccountMoney, new UpdateWrapper<UserAccountMoney>().eq("userId", userId).eq("accountId", accountId).eq("moneyType", 1));
            }
            userCapitalChangeDao.insert(new UserCapitalChange(null, userId, user.getPhone(), user.getName(), userAccountMoney.getAccountId(), user.getAccountId(), startMoney, money.longValue(), userAccountMoney.getMoney(), 1, 1, 3, null, loginInfo.getAccountUserId(), loginInfo.getPhone(), loginInfo.getName(), null, null, operaTime.getTime(), remark, originalAccountId));
            balanceChangeDao.insert(new BalanceChange(null, userId, user.getPhone(), user.getName(), userAccountMoney.getAccountId(), user.getAccountId(), startMoney, money.longValue(), userAccountMoney.getMoney(), 1, 1, 3, operaTime.getTime(), remark, null));
            //修改后余额够支付订单
            if (payRideMoney) {
                RideLog rideLog = rideLogDao.getLastByUserId(userId);
                if (rideLog != null && rideLog.getEndTime() != null && rideLog.getPayTime() == null && rideLog.getMoney() == oldRideMoney) {
                    //订单已结束并且未支付，同时用户欠款金额等于骑行订单金额，才是正常的情况，触发结束订单
                    rideLogDao.updateRideLogByOrderNO(rideLog.getOrderNO(), rideLog.getMoney().longValue(), rideLog.getRemark() == null ? "人工修改余额触发支付订单,备注:" + remark : rideLog.getRemark() + ",人工修改余额触发支付订单,修改备注:" + remark, true, operaTime);
                    rideLogDetailsDao.updatePayStatusByOrderId(rideLog.getOrderNO(), operaTime, rideLog.getMoney().longValue(), loginInfo.getId(), loginInfo.getPhone(), loginInfo.getName(), "人工修改余额触发支付订单,修改备注:" + remark);
                    Account account = accountDao.getByAccountId(userAccountMoney.getAccountId());
                    arrearsLogDao.insert(new ArrearsLog(null, userId, user.getPhone(), user.getName(), rideLog.getAccountId(), user.getAccountId(), account == null ? "未知区域" : account.getName(), rideLog.getOrderNO(), 2, 6, rideLog.getMoney(), rideLog.getMoney(), 0, operaTime.getTime(), null, loginInfo.getAccountUserId(), loginInfo.getName(), "人工修改余额触发支付订单", "人工修改余额触发支付订单,修改备注:" + remark));
                    userDelayPayDao.insert(new UserDelayPay(rideLog.getUserId(), rideLog.getAccountId(), rideLog.getMoney(), new Date(), rideLog.getOrderNO()));
                    List<UserAccountMoney> userAccountMoneyList = userAccountMoneyDao.selectList(new QueryWrapper<UserAccountMoney>().eq("userId", userId).eq("moneyType", 1).orderByDesc("money"));
                    //用户区域余额删除欠款记录
                    userAccountMoneyDao.update(new UserAccountMoney(user.getUserId(), user.getPhone(), rideLog.getAccountId(), user.getAccountId(), 0L, operaTime.getTime(), 4, "人工修改订单欠款补交"), new UpdateWrapper<UserAccountMoney>().eq("userId", user.getUserId()).eq("accountId", rideLog.getAccountId()).eq("moneyType", 4));
                    //应扣除余额
                    Long deductibleMoney = rideLog.getMoney().longValue();
                    rideUseLogDao.insert(new RideUseLog(null, rideLog.getOrderNO(), rideLog.getAccountId(), user.getAccountId(), rideLog.getMoney(), rideLog.getMoney(), 0, 0, 0, null, operaTime, null));
                    //已扣除余额
                    Long isDeductibleMoney = 0L;
                    for (UserAccountMoney userAccountMoneyTemp : userAccountMoneyList) {
                        isDeductibleMoney = updateUserAccountMoney(user, userAccountMoneyTemp, deductibleMoney - isDeductibleMoney, isDeductibleMoney, remark, loginInfo, operaTime, originalAccountId, true);
                        if (isDeductibleMoney.equals(deductibleMoney)) {
                            break;
                        }
                    }
                    // 插入用户操作日志
                    userOpLogService.insertSingle(UserOpLog.builder()
                            .accountId(user.getAccountId())
                            .opUser(loginInfo.getName())
                            .opUserPhone(loginInfo.getPhone())
                            .targetUser(user.getName())
                            .targetUserPhone(user.getPhone())
                            .operation("用户余额变动")
                            .opType("修改")
                            .opPlatform(loginInfo.getLoginUserPlatform())
                            .opTime(LocalDateTime.now())
                            .detail("变动金额: " + money / 100d + "元")
                            .build());
                } else {
                    log.error("用户有欠款并且订单异常,user={},rideLog={}", JSON.toJSONString(user), rideLog == null ? "用户无骑行订单" : JSON.toJSON(rideLog));
                }
            }
        } else {
            //扣款
            //用户区域余额
            List<UserAccountMoney> userAccountMoneyList = userAccountMoneyDao.selectList(new QueryWrapper<UserAccountMoney>().eq("userId", userId).eq("moneyType", 1).orderByDesc("money"));
            //应扣除余额
            Long deductibleMoney = (long) Math.abs(money);
            //已扣除余额
            Long isDeductibleMoney = 0L;
            for (UserAccountMoney userAccountMoney : userAccountMoneyList) {
                isDeductibleMoney = updateUserAccountMoney(user, userAccountMoney, deductibleMoney - isDeductibleMoney, isDeductibleMoney, remark, loginInfo, operaTime, originalAccountId, false);
                if (isDeductibleMoney.equals(deductibleMoney)) {
                    break;
                }
            }
            // 插入用户操作日志
            userOpLogService.insertSingle(UserOpLog.builder()
                    .accountId(user.getAccountId())
                    .opUser(loginInfo.getName())
                    .opUserPhone(loginInfo.getPhone())
                    .targetUser(user.getName())
                    .targetUserPhone(user.getPhone())
                    .operation("用户余额变动")
                    .opType("修改")
                    .opPlatform(loginInfo.getLoginUserPlatform())
                    .opTime(LocalDateTime.now())
                    .detail("变动金额: " + money / 100d + "元")
                    .build());
        }
    }

    private Long updateUserAccountMoney(User user, UserAccountMoney userAccountMoney, Long deductibleMoney, Long isDeductibleMoney, String remark, LoginInfo loginInfo, Date operaTime, Integer originalAccountId,boolean isPayRideMoney) {
        if (userAccountMoney == null) {
            return isDeductibleMoney;
        }
        Long accountMoney = userAccountMoney.getMoney() == null ? 0L : userAccountMoney.getMoney();
        if (accountMoney == 0) {
            return isDeductibleMoney;
        }
        //当前实际扣除的金额
        Long actualMoney;
        if (userAccountMoney.getMoney() >= deductibleMoney) {
            //用户余额够扣
            isDeductibleMoney += deductibleMoney;
            actualMoney = deductibleMoney;
        } else {
            actualMoney = userAccountMoney.getMoney();
            isDeductibleMoney += actualMoney;
        }
        int businessType = isPayRideMoney ? 5 : 4;
        userCapitalChangeDao.insert(new UserCapitalChange(null, user.getUserId(), user.getPhone(), user.getName(), userAccountMoney.getAccountId(), user.getAccountId(), accountMoney, actualMoney, accountMoney - actualMoney, 2, 1, businessType, null, loginInfo.getAccountUserId(), loginInfo.getPhone(), loginInfo.getName(), null, null, operaTime.getTime(), remark, originalAccountId));
        balanceChangeDao.insert(new BalanceChange(null, user.getUserId(), user.getPhone(), user.getName(), userAccountMoney.getAccountId(), user.getAccountId(), accountMoney, actualMoney, accountMoney - actualMoney, 2, 1, businessType, operaTime.getTime(), remark, null));
        userAccountMoney.setMoney(accountMoney - actualMoney).setRemark(remark).setUpdateTime(operaTime.getTime());
        userAccountMoneyDao.update(userAccountMoney, new UpdateWrapper<UserAccountMoney>().eq("userId", user.getUserId()).eq("accountId", userAccountMoney.getAccountId()).eq("moneyType", UserAccountMoneyConstant.BALANCE));
        return isDeductibleMoney;
    }

    @Override
    public User getByAdaccountId(Integer accountId, String phone) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("accountId", accountId);
        params.put("phone", phone);
        return userDao.getByAdaccountId(params);
    }

    @Override
    public List<NameObject> groupBySex(List<Integer> accountIds) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("accountIds", StringUtil.getListString(accountIds));
        return userDao.groupBySex(params);
    }

    @Override
    public List<NameObject> groupByBirth(List<Integer> accountIds) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("accountIds", StringUtil.getListString(accountIds));
        return userDao.groupByBirth(params);
    }

    @Override
    public List<NameObject> statByType(List<Integer> accountIds, String startTime, String endTime,
                                       Integer type) {
        List<NameObject> nameObjects = new ArrayList<>();
        Map<String, Object> params = new HashMap<String, Object>();
        Map<String, NameObject> stat = new HashMap<>();
        params.put("accountIds", StringUtil.getListString(accountIds));
        params.put("startTime", startTime);
        params.put("endTime", endTime);
        params.put("type", type);
        List<String> daysStr = DateTimeUtil.findDaysStr(DateTimeUtil.formatDateYMD(DateTimeUtil.StringToDateTime(startTime)), DateTimeUtil.formatDateYMD(DateTimeUtil.StringToDateTime(endTime)));
        if (type == 0) {
            List<NameObject> statParks1 = userDao.statByType(params);
            for (NameObject statPark : statParks1) {
                stat.put(statPark.getName(), statPark);
            }
            daysStr.forEach(day -> {
                if (stat.containsKey(day)) {
                    nameObjects.add(stat.get(day));
                } else {
                    nameObjects.add(new NameObject(day, 0));
                }
            });
            return nameObjects;
        }
        return userDao.statByType(params);
    }

    @Override
    public void editNameAuth(Integer userId) {
        userDao.editNameAuth(userId);
    }

    @Override
    public Integer countByType(Integer type, Integer accountId) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", type);
        params.put("accountId", accountId);
        return userDao.countByType(params);
    }

    @Override
    public DepositCount countDeposit(Integer accountId) {
        return userDao.countDeposit(accountId);
    }

    @Override
    public int updateUser(Integer userId, String phone, Integer operaUserId, String reson, LoginInfo login) {
        User user = userDao.getByUserId(userId);
        String oldPhone = user.getPhone();
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("phone", phone);
        params.put("accountId", user.getAccountId());
        List<User> users = userDao.getByAccountIdAndPhone(params);
        if (StringUtils.isNotEmpty(users)) {
            String ph = "11112110023";
            userDao.updateUser(new User(users.get(0).getUserId(), ph));
        }
        int row = userDao.updateUser(new User(userId, phone));
        if (StringUtils.isNotEmpty(users)) {
            userDao.updateUser(new User(users.get(0).getUserId(), oldPhone));
        }
        if (row > 0) {
            userPhoneLogDao.insertUserPhoneLog(new UserPhoneLog(user.getAccountId(), userId, user.getPhone(), phone, operaUserId, reson));
        }

        // 插入用户操作日志
        UserInfo loginInfo = BaseController.fetchUserInfo();
        userOpLogService.insertSingle(UserOpLog.builder()
                .accountId(user.getAccountId())
                .opUser(login.getName())
                .opUserPhone(login.getPhone())
                .targetUser(user.getName())
                .targetUserPhone(user.getPhone())
                .operation("用户账号管理")
                .opType("变更手机号")
                .opPlatform(login.getLoginUserPlatform())
                .opTime(LocalDateTime.now())
                .detail("原手机号：" + user.getPhone() + "\n新手机号：" + phone)
                .result(true)
                .build());
        return row;
    }

    @Override
    public int getUserCount(User user) {
        Map<String, Object> map = new HashMap<>();
        List<Integer> accountIds = new ArrayList<>();
        accountIds.add(user.getAccountId());
        map.put("accountIds", accountIds);
        return userDao.queryCount(map);
    }

    @Override
    public List<UserDTO> getUserList(User user, int pageNo, int excelCount) {
        return userDao.getUserList(user, pageNo, excelCount);
    }

    @Override
    public void insert(User user) {
        userDao.insert(user);
    }

    @Override
    public Map<String, User> mapByUserPhone(Set<String> phoneList, Integer accountId) {
        if (CollectionUtils.isEmpty(phoneList) || Objects.isNull(accountId)) {
            return Collections.emptyMap();
        }
        Map<String, Object> queryMap = new HashMap<>(4);
        queryMap.put("accountId", accountId);
        queryMap.put("phoneList", phoneList);
        return userDao.getByAccountIdAndPhone(queryMap).stream()
                .collect(Collectors.toMap(
                        User::getPhone, user -> user, (v1, v2) -> v1
                ));
    }

    @Override
    public Map<Integer, User> mapByUserIdAndAccountId(Set<Integer> userIds, Integer accountId) {
        if (CollectionUtils.isEmpty(userIds) || Objects.isNull(accountId)) {
            return Collections.emptyMap();
        }
        Map<String, Object> queryMap = new HashMap<>(4);
        queryMap.put("accountId", accountId);
        queryMap.put("userIds", userIds);
        return userDao.getByAccountIdAndPhone(queryMap).stream()
                .collect(Collectors.toMap(
                        User::getUserId, user -> user, (v1, v2) -> v1
                ));
    }

    @Override
    public void addUserRemark(Integer userId, String remark) {
        User user = userDao.getByUserId(userId);
        if (Objects.isNull(user)) {
            throw new BaseException(SystemCodeEnum.USER_NOT_EXIST);
        }
        user.setRemark(remark);
        userDao.updateUser(user);
    }

    @Override
    public void editNameAuthCount(Integer userId) {
        String key = RedisConstant.USER_AUTH_COUNT_PREFIX + userId;
        //获取redis的key
        String s = redisUtil.get(key);
        if (StringUtils.isNotEmpty(s)) {
            redisUtil.delete(key);
        }
        log.info("删除用户实名认证次数，用户id:{},已认证次数{}", userId, s);
    }

    @Override
    public void clearStudentAuth(Integer userId) {
        userDao.clearStudentAuth(userId);
    }

    @Transactional
    @Override
    public void batchRegisterAuth(List<BatchRegisterAuthDTO> list, Integer override,LoginInfo loginInfo) {
        if(CollectionUtils.isEmpty(list)){
            return;
        }
        List<BatchRegisterAuthDTO> existPhoneList = userDao.selectInAccountIdAndPhoneAndCountry(list);
        Map<String, BatchRegisterAuthDTO> existPhoneMap = existPhoneList.stream()
                .collect(Collectors.toMap(BatchRegisterAuthDTO::getPhone, batchRegisterAuthDTO -> batchRegisterAuthDTO));
        if(override.equals(0)){
            List<BatchRegisterAuthDTO> removeList = new ArrayList<>();
            for (BatchRegisterAuthDTO batchRegisterAuthDTO : list) {
                if(existPhoneMap.containsKey(batchRegisterAuthDTO.getPhone())){
                    removeList.add(batchRegisterAuthDTO);
                }
            }
            list.removeAll(removeList);
        }
        if(CollectionUtils.isEmpty(list)){
            return;
        }
        userDao.batchInsertOrUpdate(list);

        List<BatchRegisterAuthDTO> authList = new ArrayList<>();
        for (BatchRegisterAuthDTO batchRegisterAuthDTO : list) {
            if(org.springframework.util.StringUtils.hasLength(batchRegisterAuthDTO.getAuthNo())){
                authList.add(batchRegisterAuthDTO);
            }
        }
        if(CollectionUtils.isEmpty(authList)){
            return;
        }
        List<BatchRegisterAuthDTO> needAuthList = userDao.selectInAccountIdAndPhoneAndCountry(authList);
        needAuthList.forEach(batchRegisterAuthDTO -> {
            batchRegisterAuthDTO.setAccountUserId(loginInfo.getAccountUserId());
        });
        studentAuthApplyDao.batchInsertUpdate(needAuthList);
    }

    @Override
    public String batchRegisterAuthCheck(List<BatchRegisterAuthDTO> list) {
        if(CollectionUtils.isEmpty(list)){
            return "";
        }
        List<BatchRegisterAuthDTO> existPhoneList = userDao.selectInAccountIdAndPhoneAndCountry(list);
        if(CollectionUtils.isEmpty(existPhoneList)){
            return "";
        }
        String collect = existPhoneList.stream().map(BatchRegisterAuthDTO::getPhone).collect(Collectors.joining(","));
        return collect;
    }

    /**
     * 将品牌下所有用户的登录状态设置为过期(将token置为过期)
     * @param accountId 品牌id
     */
    @Override
    public void expireLogin(Integer accountId) {
        log.info("品牌下所有用户的登录状态设置为过期,品牌id:{}", accountId);
        long start = System.currentTimeMillis();
        String tokenPrefix = RedisKeyConstant.getTokenPrefix(accountId);
        redisService.delByPrefixPipeline(tokenPrefix);

        String userIdToTokenPrefix = RedisKeyConstant.getTokenByUserIdPrefix(accountId);
        redisService.delByPrefixPipeline(userIdToTokenPrefix);
        log.info("品牌下所有用户的登录状态设置为过期,品牌id:{},耗时:{}ms", accountId, System.currentTimeMillis() - start);
    }

    @Override
    public String batchExport(User user, boolean exportBrandName, String sheetName) throws FileNotFoundException {
        long start = System.currentTimeMillis();
        int userCount = getUserCount(user);
        int pageNo = 0;
        int excelCount = ExcelUtil.SHEET_SIZE * 5 - 10;
        List<File> files = new ArrayList<>();
        Map<Integer, String> brandMap = new HashMap<>(64);
        while (userCount > excelCount) {
            List<UserDTO> users = getUserList(user, pageNo * excelCount, excelCount);
            paddingBrandName(exportBrandName, brandMap, users);
            ExcelUtil<UserDTO> util = new ExcelUtil<>(UserDTO.class);
            userCount -= excelCount;
            pageNo++;
            files.add(new File(OperaPswConstant.PATH + util.exportExcel(users, sheetName).getData()));
        }
        List<UserDTO> users = getUserList(user, pageNo * excelCount, excelCount);
        //填充品牌名称
        paddingBrandName(exportBrandName, brandMap, users);
        ExcelUtil<UserDTO> util = new ExcelUtil<>(UserDTO.class);
        String fileName = (String) util.exportExcel(users, sheetName).getData();
        String s = fileName.replaceAll("xlsx", "zip");
        files.add(new File(OperaPswConstant.PATH + fileName));
        ZipUtils.toZip(files, new FileOutputStream(OperaPswConstant.PATH + s));
        files.forEach(name -> FileUtils.deleteFile(name.getAbsolutePath()));
        LogUtil.info("=========用户数据导出耗时：" + (System.currentTimeMillis() - start) + "毫秒=========");
        return s;
    }

    private void paddingBrandName(boolean exportBrandName, Map<Integer, String> brandMap, List<UserDTO> users) {
        if (exportBrandName) {
            users.forEach(x -> {
                Integer accountId = x.getAccountId();
                String brandName = brandMap.computeIfAbsent(accountId, acId -> Optional.ofNullable(accountDao.getByAccountId(acId)).map(Account::getName).orElse(""));
                x.setBrandName(brandName);
            });
        }
    }

    @Override
    public List<NameObject> statByTypePlus(List<Integer> accountIds, Date startTime, Date endTime, Integer type) {
        List<NameObject> list = new LinkedList<>();
        List<String> betweenEveryDay = DateTimeUtil.getBetweenEveryDay(startTime, endTime);
        List<AccountDayStat> accountDayStatList = accountDayStatDao.selectList(new QueryWrapper<AccountDayStat>().in("accountId", accountIds).between("dataDate", DateTimeUtil.parseDataDate(startTime), DateTimeUtil.parseDataDate(endTime)).eq("dataType", AccountDayStatEnum.NUMBER_OF_NEW_USERS.getDataType()));

        Map<String, Long> map = new HashMap<>();
        for (AccountDayStat a : accountDayStatList) {
            Long v = map.get(a.getDataDate().toString());
            map.put(a.getDataDate().toString(), v == null ? a.getCntTotal() : v + a.getCntTotal());
        }
        switch (type) {
            case 0:
                //按天
                for (String s : betweenEveryDay) {
                    Long value = map.get(s.replaceAll("-", ""));
                    list.add(new NameObject(s, value == null ? 0 : value));
                }
                break;
            case 1:
                //按月
                Map<String, Long> monthMap = new HashMap<>();
                List<String> monthList = new ArrayList<>();
                for (String s : betweenEveryDay) {
                    String key = s.replaceAll("-", "");
                    Long value = map.get(key);
                    String monthMapKey = key.substring(0, 6);
                    Long monthValue = monthMap.get(monthMapKey);
                    if (monthValue == null) {
                        monthMap.put(monthMapKey, value == null ? 0L : value);
                    } else {
                        monthMap.put(monthMapKey, value == null ? monthValue : value + monthValue);
                    }
                    String monthStr = s.substring(0, 7);
                    if (!monthList.contains(monthStr)) {
                        monthList.add(monthStr);
                    }
                }
                for (String s : monthList) {
                    Long value = monthMap.get(s.replaceAll("-", ""));
                    list.add(new NameObject(s, value == null ? 0 : value));
                }
                break;
            default:
                break;
        }
        return list;
    }

    @Override
    public List<User> getIdByAccountIdAndPhoneAndNameAndIdNo(Integer accountId, String phone, String name, String idNo) {
        return userDao.getIdByAccountIdAndPhoneAndNameAndIdNo(accountId, phone, name, idNo);
    }

    @Override
    public UserVO getInfoByUserId(Integer userId, String accountIds) {
        List<String> accountIdStrList = StringUtils.str2List(accountIds, ",", true, true);
        List<Integer> accountIdList = accountIdStrList.stream().map(Integer::parseInt).collect(Collectors.toList());
        UserVO user = userDao.getInfoByUserId(userId);
        Integer brandId = accountService.getBrandId(accountIdList.get(0));
        /**用户特殊认证*/
        UserAuthLog userAuthLog = userAuthLogService.selectByUserId(userId);
        user.setSpecialAuth(false);
        if (userAuthLog != null){
            user.setSpecialAuth(true);
            user.setSpecialAuthNO(userAuthLog.getAuthNo());
        }

        /**根据区域筛选用户最近一笔订单*/
        RideLog rideLog = rideLogDao.getLastByUserIdAndAccountId(userId, accountIdList);
        user.setRideLog(rideLog);

        /**用户骑行状态*/
        user.setRideStatus(1);
        if (rideLog != null && rideLog.getEndTime() == null){
            user.setRideStatus(2);
        }
        if (user.getIsBlack()){
            user.setRideStatus(4);
        }
        if (user.getRideMoney() > 0){
            user.setRideStatus(3);
        }

        /**费用申诉*/
        FeeAppeal feeAppeal = new FeeAppeal();
        feeAppeal.setAccountIds(accountIdList);
        feeAppeal.setPhone(user.getPhone());
        feeAppeal.setStartTime(DateUtil.beginOfDay(DateUtil.offsetDay(new Date(), -30)));
        feeAppeal.setEndTime(DateUtil.endOfMonth(new Date()));
        List<FeeAppeal> feeAppealList = orderFeeAppealService.getInfo(feeAppeal);
        long feeAppealAlready = feeAppealList.stream().filter(item -> item.getState() != 0).count();
        user.setFeeAppealAlready((int) feeAppealAlready);
        user.setFeeAppealAll(feeAppealList.size());

        /**意见反馈*/
        List<Suggestion> suggestionList = suggestionService.selectByAccountIdList(userId, accountIdList,
                DateUtil.beginOfDay(DateUtil.offsetDay(new Date(), -30)), new Date());
        long suggestionAlready = suggestionList.stream().filter(item -> item.getState() != 0).count();
        user.setSuggestionAlready((int) suggestionAlready);
        user.setSuggestionAll(suggestionList.size());

        /**待开发票*/
        List<InvoiceRecord> invoiceRecordList = invoiceRecordService.selectList(userId, brandId,
                DateUtil.beginOfDay(DateUtil.offsetDay(new Date(), -30)), new Date());
        long invoiceAlready = invoiceRecordList.stream().filter(item -> item.getStatus() != 0).count();
        user.setInvoiceAlready((int) invoiceAlready);
        user.setInvoiceAll(invoiceRecordList.size());

        /**退押金申请*/
        List<DepositApply> depositApplyList = depositApplyService.selectList(userId, brandId,
                DateUtil.beginOfDay(DateUtil.offsetDay(new Date(), -30)), new Date());
        long depositAlready = depositApplyList.stream().filter(item -> item.getState() != 0).count();
        user.setDepositAlready((int) depositAlready);
        user.setDepositAll(depositApplyList.size());

        /**站点申请*/
        List<ParkPointApply> parkPointApplyList = parkPointApplyService.selectList(userId, accountIdList,
                DateUtil.beginOfDay(DateUtil.offsetDay(new Date(), -30)), new Date());
        long parkPointApplyAlready = parkPointApplyList.stream().filter(item -> item.getState() != 0).count();
        user.setParkPointApplyAlready((int) parkPointApplyAlready);
        user.setParkPointApplyAll(parkPointApplyList.size());

        /**用户骑行卡*/
        UserMember rideCard = rideCardService.userRideCardByAccountIdList(userId, accountIdList);
        //骑行卡在查询时需要先更新是否失效状态
        if (rideCard != null){
            if (new Date().compareTo(DateTimeUtil.StringToDateTime(rideCard.getExpireTime() + " 23:59:59")) > 0
                    && Objects.equals(rideCard.getUserType(), 0)) {
                rideCard.setIsValid(0);
                rideCardService.updateValid(rideCard);
            }
            if (rideCard.whetherDailyCardAndResetCurrentNum()) {
                rideCardService.updateCurrentNumByOrderNo(rideCard.getOrderNO(), 0);
            }
        }
        user.setRideCard(rideCard);

        /**用户会员卡*/
        UserMember vipCard = vipCardService.userVipPageByAccountIdList(userId, accountIdList);
        user.setVipCard(vipCard);

        /**用户免押卡*/
        UserMember depositCard = memberFeeService.userVipPageByAccountIdList(userId, accountIdList);
        user.setDepositCard(depositCard);
        AgentDepositVip agentDepositVip = agentDepositVipService.selectByAccountIdList(userId, accountIdList);
        if (agentDepositVip != null) {
            user.setExpireTime(agentDepositVip.getExpireDt());
        }

        /**用户优惠券*/
        CouponUser coupon = couponService.selectByAccountIdList(userId, accountIdList);
        user.setCoupon(coupon);
        user.desensitization();
        return user;
    }
}
